// /////////////////////////////////////////////////////////////////////////////
//
// Name:            Event.java
// Author:          Michael Bartsch
//
// Desc :           Defines the Event class.
//
// License:         Copyright (C) 2009 Michael Bartsch
//
//                  This software is provided 'as-is', without any express or
//                  implied warranty. In no event will the authors be held
//                  liable for any damages arising from the use of this
//                  software.
//
//                  Permission is granted to anyone to use this software for
//                  any purpose, including commercial applications, and to
//                  alter it and redistribute it freely, subject to the
//                  following restrictions:
//
//                  1. The origin of this software must not be misrepresented;
//                  you must not claim that you wrote the original software. If
//                  you use this software in a product, an acknowledgment in the
//                  product documentation would be appreciated but is not
//                  required.
//
//                  2. Altered source versions must be plainly marked as such,
//                  and must not be misrepresented as being the original
//                  software.
//
//                  3. This notice may not be removed or altered from any source
//                  distribution.
//
// /////////////////////////////////////////////////////////////////////////////

// Package
package net.dotsimplicity.events;

// Import classes
import java.lang.reflect.Method;
import java.util.LinkedList;


// Event class
/**
 * The Event class defines an event and the parameters that each subscribed {@link EventSlot} should 
 * implement. In order to provide these services each Event consists of a name, a collection of
 * parameters and a collection of EventSlots (which provide the required parameters).
 */
public class Event 
{
	// Public methods
	/**
	 * Constructs an Event with a name and no required parameters.
	 * @param name           Name of the Event
	 */
	public Event(String name)
	{
	   this.eventName = name;
	   this.parameters = null;
	   
	   this.eventSlots = new LinkedList<EventSlot>();
	}
	
	/**
	 * Constructs an Event with a name and one parameter.
	 * @param name           Name of the Event
	 * @param parameter      Required parameter that subscribed EventSlots should implement
	 */
	public Event(String name, Class<?> parameter)
	{
	   this.eventName = name;
		
	   Class<?> [] parameters = { parameter };
	   this.parameters = parameters;
		   
	   this.eventSlots = new LinkedList<EventSlot>();
	}
	
	/**
	 * Constructs an Event with a name and several parameters.
	 * @param name           Name of the Event
	 * @param parameters     Array of required parameters that subscribed Eventslots should 
	 *                       implement
	 */
    public Event(String name, Class<?> [] parameters)
    {
       this.eventName = name;
       this.parameters = parameters;
       
       this.eventSlots = new LinkedList<EventSlot>();
    }
    
    /**
     * Connects the method with the given name and the required parameters of the given object to 
     * this event.
     * @param object         Instance of the object
     * @param methodName     Name of the method
     */
    public void connectEventSlot(Object object, String methodName)
    {
       // Check if we got a valid object.
       if(object == null)
    	 throw new NullPointerException("Event(" + this.eventName + ").connectEventSlot: Object/Class required\n");
       
       // Check if we got a valid method.
       Method method;
       
       try
       {
          method = object.getClass().getMethod(methodName, this.parameters);
       } 
       catch(NoSuchMethodException e) 
       {
    	  throw new RuntimeException("Event(" + this.eventName + ").connectEventSlot: No method \"" + e.getMessage() + "\"\n");
	   }
       
       // Connect the event slot.
       this.connectEventSlot(object, method);
    }
    
    /**
     * Connects the static method with the given name and the required parameters of the given 
     * class to this event.
     * @param classReference Reference to the class
     * @param methodName     Name of the method
     */
    public void connectEventSlot(Class<?> classReference, String methodName)
    {
       // Check if we got a valid object.
       if(classReference == null)
    	 throw new NullPointerException("Event(" + this.eventName + ").connectEventSlot: Object/Class required\n");
       
       // Check if we got a valid method.
       Method method;
       
       try
       {
          method = classReference.getMethod(methodName, this.parameters);
       } 
       catch(NoSuchMethodException e) 
       {
    	  throw new RuntimeException("Event(" + this.eventName + ").connectEventSlot: No method \"" + e.getMessage() + "\"\n");
	   }
       
       // Connect the event slot.
       this.connectEventSlot(classReference, method);
    }
    
    /**
     * Disconnects the method with the given name and the required parameters of the given object 
     * from this event.
     * @param object         Instance of the object
     * @param methodName     Name of the method
     */
    public void disconnectEventSlot(Object object, String methodName)
    {
       // Check if we got a valid object.
       if(object == null)
    	 throw new NullPointerException("Event(" + this.eventName + ").disconnectEventSlot: Object/Class required\n");
        
       // Check if we got a valid method.
       Method method;
        
       try
       {
          method = object.getClass().getMethod(methodName, this.parameters);
       } 
       catch (NoSuchMethodException e) 
       {
    	  throw new RuntimeException("Event(" + this.eventName + ").disconnectEventSlot: No method \"" + e.getMessage() + "\"\n");
 	   }
        
       // Disconnect event slot.
       this.disconnectEventSlot(object, method);
    }
    
    /**
     * Disonnects the static method with the given name and the required parameters of the given 
     * class from this event.
     * @param classReference Reference to the class
     * @param methodName     Name of the method
     */
    public void disconnectEventSlot(Class<?> classReference, String methodName)
    {
       // Check if we got a valid object.
       if(classReference == null)
    	 throw new NullPointerException("Event(" + this.eventName + ").disconnectEventSlot: Object/Class required\n");
        
       // Check if we got a valid method.
       Method method;
        
       try
       {
          method = classReference.getMethod(methodName, this.parameters);
       } 
       catch (NoSuchMethodException e) 
       {
    	  throw new RuntimeException("Event(" + this.eventName + ").disconnectEventSlot: No method \"" + e.getMessage() + "\"\n");
 	   }
        
       // Disconnect event slot.
       this.disconnectEventSlot(classReference, method);
    }
    
    /**
     * Emits an event signal with no arguments.
     */
    public void emitEventSignal()
    {
       this.emitEventSignal(null);
    }
    
    /**
     * Emits an event signal with one argument.
     * @param argument       Argument which will be transmitted together with the signal
     */
    public void emitEventSignal(Object argument)
    {
       Object [] arguments = { argument };
       this.emitEventSignal(arguments);
    }
    
    /**
     * Emits an event signal with several arguments.
     * @param arguments      Arguments which will be transmitted together with the signal
     */
    public void emitEventSignal(Object [] arguments)
    {
       // Check if the arguments match with the parameters.       
       if(this.parameters != null && arguments != null)
       {
          if(this.parameters.length == arguments.length)
          {
             for(int i = 0; i < this.parameters.length; i++)
             {
                if(this.parameters[i] != arguments[i].getClass())
                  throw new IllegalArgumentException("Event(" + this.eventName + ").emitEventSignal: Illegal arguments\n");
             }
          }
          
          else throw new IllegalArgumentException("Event(" + this.eventName + ").emitEventSignal: Illegal arguments\n");
       }
       
       else if(this.parameters == null && arguments == null)
    	 ; // We can continue executing this method.
       
       else throw new IllegalArgumentException("Event(" + this.eventName + ").emitEventSignal: Illegal arguments\n");
    	   
       // Emit the signal.       
       for(EventSlot slot : this.eventSlots)
       {
          try
          {
    	     slot.emitEventSignal(arguments);
          }
          catch(NullPointerException e)
          {
        	 throw new IllegalArgumentException("Event(" + this.eventName + ")." + e.getMessage());
          }
       }
    }
    
    /**
     * Returns the name of the Event.
     */
    public String getName()
    {
       return this.eventName;
    }
    
    // Private methods
    /**
     * Connects the given object and method to this Event.
     * @param object         Instance of the object
     * @param method         Reference to the Method instance of the method
     */
    private void connectEventSlot(Object object, Method method)
    {
    	// Check if we already have an entry for this object and method.
        if(this.getEventSlot(object, method) != null)
     	  throw new RuntimeException("Event(" + this.eventName + ").connectEventSlot: Slot already exists\n");
        
        // Create a new event slot and add it to our list.
        EventSlot slot = new EventSlot(object, method);
        this.eventSlots.add(slot);
    }
    
    /**
     * Disconnects the given object and method from this Event.
     */
    private void disconnectEventSlot(Object object, Method method)
    {
    	// Retrieve a reference to the event slot.
        EventSlot slot = this.getEventSlot(object, method);
         
        if(slot == null)
          throw new RuntimeException("Event(" + this.eventName + ").disconnectEventSlot: Slot does not exist\n");
         
        // Remove the event slot from our list.
        this.eventSlots.remove(slot);
    }
    
    /**
     * Returns the {@link EventSlot} with the given name.
     */
    private EventSlot getEventSlot(Object object, Method method)
    {
       // Try to retrieve the requested event slot.
       for(EventSlot slot : this.eventSlots)
       {
          if(slot.getObject().equals(object) && slot.getMethod().equals(method))
        	return slot;
       }
       
       // We didn't find the event slot.
       return null;
    }
    
    // Private members
    private String eventName;
    private Class<?> [] parameters;
    private LinkedList<EventSlot> eventSlots;
}

// End of File
